package org.cmroot.certmaster.service.impl;

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.util.ASN1Dump;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.GeneralName;
import org.cmroot.certmaster.service.ASN1Engine;
import org.cmroot.certmaster.service.ASN1EngineException;
import org.cmroot.certmaster.service.OidDecoder;
import org.cmroot.certmaster.service.Utils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import sun.security.x509.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;

/**
 * Copyright (c) 2011 Andrey Domas <andrey.domas at gmail.com>.
 * *
 * This file is part of certmaster.
 * *
 * Certmaster is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * *
 * Certmaster is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see http://www.gnu.org/licenses
 * or write to the Free Software Foundation,Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301  USA
 */

@Service
@Qualifier("asn1_bc_engine")
public class ASN1EngineBCImpl implements ASN1Engine {

    private static DERObject decodeDER(byte[] data) throws IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
        ASN1InputStream asn1InputStream = new ASN1InputStream(inputStream);
        return asn1InputStream.readObject();
    }

    private static String decodeDERUTF8String(DEREncodable derObj) {
        return DERUTF8String.getInstance(derObj).getString();
    }

    private static String decodeDERIA5String(DEREncodable derObj) {
        return DERIA5String.getInstance(derObj).getString();
    }

    private static String decodeDERObjectIdentifier(DEREncodable derObj) {
        String s = OidDecoder.lookup(
                DERObjectIdentifier.getInstance(derObj).getId());

        if (s != null) {
            return null;
        } else {
            return s;
        }
    }

    private static String decodeDEROctetString(DEREncodable derObj) {
        return Utils.byteArray2HexString(DEROctetString.getInstance(derObj).getOctets());
    }

    private static String decodeDERGeneralizedTime(DEREncodable derObj) {
        return DERGeneralizedTime.getInstance(derObj).getTime();
    }

    private static Enumeration decodeDERSet(DEREncodable derObj) {
        return DERSet.getInstance(derObj).getObjects();
    }

    private static Enumeration decodeDERSequence(DEREncodable derObj) {

        ASN1Sequence asn1Sequence = DERSequence.getInstance(derObj);
        return asn1Sequence.getObjects();
    }

    private static Object decodeDERTaggedObject(DEREncodable derObj) throws IOException {

        ASN1TaggedObject asn1TaggedObject = DERTaggedObject.getInstance(derObj);

        DERObject expDerObj = asn1TaggedObject.getObject();

        if (expDerObj instanceof ASN1Sequence) {

            return decodeDERSequence(expDerObj);

        } else if (expDerObj instanceof ASN1Set) {

            return decodeDERSet(expDerObj);

        } else if (expDerObj instanceof DEROctetString) {

            return decodeDEROctetString(expDerObj);

        }

        return null;

    }

    private static List<String> _nullOrString(String s) {
        if (s != null) {
            List<String> result_list = new ArrayList<String>();
            result_list.add(s);
            return result_list;
        }
        return null;
    }

    private static List<String> _nullOrList(List<String> list) {
        if (list != null && list.size() > 0) {
            return list;
        }
        return null;
    }

    private static String _getOneFromList(List<String> list) {
        if (list != null && list.size() > 0) {
            return list.iterator().next();
        }
        return null;
    }

    private static List<String> processDEROctetString(DEREncodable derObj) throws IOException {

        Object unknownObj;

        if (derObj instanceof DEROctetString) {

            unknownObj = decodeDEROctetString(derObj);

        } else if (derObj instanceof DERIA5String) {

            unknownObj = decodeDERIA5String(derObj);

        } else if (derObj instanceof DERUTF8String) {

            unknownObj = decodeDERUTF8String(derObj);

        } else if (derObj instanceof DERSequence) {

            unknownObj = decodeDERSequence(derObj);

        } else if (derObj instanceof DERObjectIdentifier) {

            unknownObj = decodeDERObjectIdentifier(derObj);

        } else if (derObj instanceof DERTaggedObject) {

            unknownObj = decodeDERTaggedObject(derObj);

        } else if (derObj instanceof DERSet) {

            unknownObj = decodeDERSet(derObj);

        } else {
            return null;
        }

        if (unknownObj instanceof String) {

            return _nullOrString((String) unknownObj);

        } else if (unknownObj instanceof Enumeration) {

            List<String> list = new ArrayList<String>();

            while (((Enumeration) unknownObj).hasMoreElements()) {
                Object enumDerObject = ((Enumeration) unknownObj).nextElement();
                List<String> tmpList = processDEROctetString((DEREncodable) enumDerObject);
                if (tmpList != null) {
                    list.addAll(tmpList);
                }
            }

        }

        return null;

    }

    private static List<String> printAuthorityInfoAccess(DEREncodable derObj) throws IOException {
        AuthorityInformationAccess authorityInformationAccess =
                AuthorityInformationAccess.getInstance(derObj);
        if (authorityInformationAccess != null) {

            List<String> list = new ArrayList<String>();
            StringBuilder strb = new StringBuilder();

            for (AccessDescription ad : authorityInformationAccess.getAccessDescriptions()) {
                strb.append(OidDecoder.lookup(ad.getAccessMethod().toString()));
                strb.append(": ");
                strb.append(
                        _getOneFromList(
                                processDEROctetString(
                                        ad.getAccessLocation().getName()
                                )));
                list.add(strb.toString());
            }
            return list;
        }
        return null;
    }

    private static List<String> printSubjectKeyIdentifier(DEREncodable derObj) throws IOException {
        SubjectKeyIdentifier subjectKeyIdentifier = SubjectKeyIdentifier.getInstance(derObj);

        if (subjectKeyIdentifier != null) {
            return _nullOrString(Utils.byteArray2HexString(subjectKeyIdentifier.getKeyIdentifier()));
        }
        return null;
    }

    private static List<String> printPrivateKeyUsagePeriod(DEREncodable derObj) throws IOException {
        PrivateKeyUsagePeriod privateKeyUsagePeriod = PrivateKeyUsagePeriod.getInstance(derObj);

        if (privateKeyUsagePeriod != null) {
            List<String> list = new ArrayList<String>();

            list.add("Not Before: " + decodeDERGeneralizedTime(privateKeyUsagePeriod.getNotBefore()));
            list.add("Not After: " + decodeDERGeneralizedTime(privateKeyUsagePeriod.getNotAfter()));
            return list;
        }
        return null;
    }

    private static String decodeGeneralName(int type) {
        switch (type) {
            case GeneralName.directoryName:
                return "dirName";
            case GeneralName.dNSName:
                return "DNS";
            case GeneralName.ediPartyName:
                return "ediPartyName";
            case GeneralName.iPAddress:
                return "iPAddress";
            case GeneralName.otherName:
                return "otherName";
            case GeneralName.registeredID:
                return "registeredID";
            case GeneralName.rfc822Name:
                return "RFC822";
            case GeneralName.uniformResourceIdentifier:
                return "URI";
            case GeneralName.x400Address:
                return "X400";

        }
        return null;
    }

    private static List<String> printAuthorityKeyIdentifier(DEREncodable derObj) throws IOException {
        AuthorityKeyIdentifier authorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(derObj);

        if (authorityKeyIdentifier != null) {
            List<String> list = new ArrayList<String>();

            list.add("keyid:" +
                    Utils.byteArray2HexString(
                            authorityKeyIdentifier.getKeyIdentifier()));

            if (authorityKeyIdentifier.getAuthorityCertIssuer() != null) {
                GeneralName names[] = authorityKeyIdentifier.getAuthorityCertIssuer().getNames();
                for (GeneralName gn : names) {
                    list.add(
                            decodeGeneralName(gn.getTagNo()) + ":" +
                                    _getOneFromList(processDEROctetString(gn.getName()))
                    );
                }

                list.add("serial:" + authorityKeyIdentifier.getAuthorityCertSerialNumber().toString());
            }

            return list;
        }
        return null;
    }

    private static List<String> printCRLDistributionPoints(DEREncodable debObj) throws IOException {
        CRLDistPoint crlDistPoint = CRLDistPoint.getInstance(debObj);

        if (crlDistPoint != null) {
            List<String> list = new ArrayList<String>();
            for (DistributionPoint distributionPoint : crlDistPoint.getDistributionPoints()) {
                StringBuilder strb = new StringBuilder();

                strb.append(decodeGeneralName(
                        distributionPoint.getDistributionPoint().getType())).append(":");
                strb.append(
                        _getOneFromList(
                                processDEROctetString(
                                        distributionPoint.getDistributionPoint().getName().getDERObject()))).append("\n");
                list.add(strb.toString());
            }
            return list;
        }
        return null;
    }

    private static List<String> printSubjectDirectoryAttributes(DEREncodable derObj) throws IOException {
        SubjectDirectoryAttributes subjectDirectoryAttributes = SubjectDirectoryAttributes.getInstance(derObj);

        if (subjectDirectoryAttributes != null && !subjectDirectoryAttributes.getAttributes().isEmpty()) {
            Iterator attributes_it = subjectDirectoryAttributes.getAttributes().iterator();
            List<String> list = new ArrayList<String>();

            while (attributes_it.hasNext()) {
                List<String> tmp_list = _nullOrString(decodeDEROctetString((DEREncodable) attributes_it.next()));
                if (tmp_list != null) {
                    list.addAll(tmp_list);
                }
            }
            return list;
        }
        return null;
    }

    @Override
    public List<String> printASN1(String oid, byte[] data) throws ASN1EngineException {

        DERObjectIdentifier objOid= null;
        if (oid != null) {
            objOid = new DERObjectIdentifier(oid);
        }

        try {
            DERObject der = decodeDER(data);
            if (der instanceof DEROctetString) {
                der = decodeDER(((DEROctetString) der).getOctets());

                if (objOid != null) {
                    if (objOid.equals(X509Extensions.SubjectDirectoryAttributes)) {
                        return printSubjectDirectoryAttributes(der);
                    } else if (objOid.equals(X509Extensions.SubjectKeyIdentifier)) {
                        return printSubjectKeyIdentifier(der);
                    } else if (objOid.equals(X509Extensions.KeyUsage)) {
                    } else if (objOid.equals(X509Extensions.PrivateKeyUsagePeriod)) {
                        return printPrivateKeyUsagePeriod(der);
                    } else if (objOid.equals(X509Extensions.SubjectAlternativeName)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.IssuerAlternativeName)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.BasicConstraints)) {
                    } else if (objOid.equals(X509Extensions.CRLNumber)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.ReasonCode)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.InstructionCode)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.InvalidityDate)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.DeltaCRLIndicator)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.IssuingDistributionPoint)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.CertificateIssuer)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.NameConstraints)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.CRLDistributionPoints)) {
                        return printCRLDistributionPoints(der);
                    } else if (objOid.equals(X509Extensions.CertificatePolicies)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.PolicyMappings)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.AuthorityKeyIdentifier)) {
                        return printAuthorityKeyIdentifier(der);
                    } else if (objOid.equals(X509Extensions.PolicyConstraints)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.ExtendedKeyUsage)) {
                    } else if (objOid.equals(X509Extensions.FreshestCRL)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.InhibitAnyPolicy)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.AuthorityInfoAccess)) {
                        return printAuthorityInfoAccess(der);
                    } else if (objOid.equals(X509Extensions.SubjectInfoAccess)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.LogoType)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.BiometricInfo)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.QCStatements)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.AuditIdentity)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.NoRevAvail)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    } else if (objOid.equals(X509Extensions.TargetInformation)) {
                        return _nullOrString(ASN1Dump.dumpAsString(der));
                    }
                }

                return processDEROctetString(der);

            }

        } catch (IOException e) {
            throw new ASN1EngineException(e);
        }

        return null;

    }

}
